#include "file_io.h"

FileIO::FileIO(wchar_t* filename, bool output, DWORD dw_file_size_high, DWORD dw_file_size_low) {
  file_ = NULL;
  output_ = false;
  file_map_ = INVALID_HANDLE_VALUE;
  loadFile(filename, output, dw_file_size_high, dw_file_size_low);
}

FileIO::~FileIO() {
  CloseHandle(_file);
  CloseHandle(file_map_);
}

HANDLE FileIO::createMapOfFile() {
  if (_file == INVALID_HANDLE_VALUE) {
    return INVALID_HANDLE_VALUE;
  }

  DWORD fl_protect;
  if (!output_) {
    fl_protect = PAGE_READONLY;
  } else {
    fl_protect = PAGE_READWRITE;
  }

  return CreateFileMapping(_file, NULL, fl_protect, dw_file_size_high_,
                           dw_file_size_low_, NULL);
}

HANDLE FileIO::openFile() {
  DWORD dwDesiredAccess, dwCreationDisposition;
  if (!output_) {
    dwDesiredAccess = GENERIC_READ;
    dwCreationDisposition = OPEN_EXISTING;
  } else {
    dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
    dwCreationDisposition = CREATE_ALWAYS;
  }
  return CreateFile(file_, dwDesiredAccess, NULL, NULL, dwCreationDisposition,
                    FILE_FLAG_SEQUENTIAL_SCAN, NULL);
}

bool FileIO::loadFile(wchar_t* filename, bool output, DWORD dw_file_size_high, DWORD dw_file_size_low) {
  CloseHandle(file_map_);
  file_ = filename;
  output_ = output;
  _file = openFile();
  if (output_) {
    dw_file_size_high_ = dw_file_size_high;
    dw_file_size_low_ = dw_file_size_low;
  } else {
    dw_file_size_low_ = GetFileSize(_file, &dw_file_size_high_);
  }
  file_map_ = createMapOfFile();
  dw_file_offset_high_ = 0;
  dw_file_offset_low_ = 0;
  return file_map_ != INVALID_HANDLE_VALUE;
}

char* FileIO::getNextMap(bool* is_last_map) {
  if (file_map_ == INVALID_HANDLE_VALUE) {
    return NULL;
  }
  DWORDLONG file_size = dw_file_size_high_ * DWORD_HIGH + dw_file_size_low_;
  DWORDLONG offset = dw_file_offset_high_ * DWORD_HIGH + dw_file_offset_low_;
  if (offset >= file_size) {
    if (is_last_map != NULL) {
      *is_last_map = true;
    }
    return NULL;
  }
  SIZE_T size = file_size - offset;
  bool last_map = true;
  if (size > SIZE_OF_BUFFER) {
    size = SIZE_OF_BUFFER;
    last_map = false;
  }
  if (is_last_map != NULL) {
    *is_last_map = last_map;
  }

  DWORD desired_access;
  if (!output_) {
    desired_access = FILE_MAP_READ;
  } else {
    desired_access = FILE_MAP_WRITE;
  }
  char* map = (char*)MapViewOfFile(file_map_, desired_access, dw_file_offset_high_, dw_file_offset_low_, size);
  if (map != NULL) {
    // Map to last 'space' symbol if not end of file
    if (!last_map) {
      while (map[--size] != ' ' || size == 0);
      // Restore correct value of size
      if (size != 0) {
        size++;
      }
    }
    increaseOffsets(size);
  }
  return map;
}

void FileIO::increaseOffsets(SIZE_T size) {
  // Diff between max value of ulong and current low offset
  DWORD low_offset_left = (dw_file_offset_low_ == 0) ? UINT_MAX : DWORD_HIGH - dw_file_offset_low_;
  if (low_offset_left > size) {
    dw_file_offset_low_ += size;
  } else {
    dw_file_offset_high_++;
    dw_file_offset_low_ = size - low_offset_left;
  }
}

DWORD FileIO::getFileSize(DWORD* dw_file_size_high) {
  *dw_file_size_high = dw_file_size_high_;
  return dw_file_size_low_;
}